package com.szms.mobile.repository;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import androidx.annotation.NonNull;

import com.szms.mobile.annotations.Column;
import com.szms.mobile.annotations.Table;
import com.szms.mobile.common.ITypeConverter;
import com.szms.mobile.dto.db.ColumnDto;
import com.szms.mobile.repository.model.BaseModel;
import com.szms.mobile.util.ReflectionUtils;
import com.szms.mobile.util.SqliteHelper;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public abstract class BaseRepository<T extends BaseModel> {

    protected Context context;

    abstract Class<T> getModelCls();

    public BaseRepository(Context _context){
        this.context = _context;
    }

    /**
     * 创建表
     */
    public static <T extends BaseModel> String createTable(SQLiteDatabase database,Class<T> cls) {

        if (!cls.isAnnotationPresent(Table.class)) {
            return null;
        }

        Table table = cls.getAnnotation(Table.class);

        List<ColumnDto> columns = new ArrayList<>();
        findColumns(cls, columns);

        StringBuffer sqlBuffer = new StringBuffer("create table " + table.value() + " ( ");
        for (int i = 0; i < columns.size(); i++) {
            ColumnDto column = columns.get(i);
            sqlBuffer.append(column.getColumnName() + " " + column.getColumnType().getCode());
            if (column.isPrimaryKey()) {
                sqlBuffer.append(" primary key");
            }
            if (column.isAutoIncrement()) {
                sqlBuffer.append(" autoincrement");
            }
            if (i < columns.size() - 1) {
                sqlBuffer.append(", ");
            }
        }
        sqlBuffer.append(" )");

        return sqlBuffer.toString();
    }


    /**
     * 添加
     *
     * @param item
     * @return
     */
    public boolean add(T item) {
        Class<T> cls = getModelCls();
        if (!cls.isAnnotationPresent(Table.class)) {
            return false;
        }

        SQLiteDatabase database = SqliteHelper.Instance(context).getWritableDatabase();

        if (!database.isOpen()) {
            Log.d("", "db is not open.");
            return false;
        }

        Table table = cls.getAnnotation(Table.class);

        ContentValues values = getContentValues(item);
        database.insert(table.value(), null, values);

        database.close();
        return true;
    }

    /**
     * 删除
     * @param id
     * @return
     */
    public boolean delete(Integer id){
        Class<T> cls = getModelCls();
        if (!cls.isAnnotationPresent(Table.class)) {
            return false;
        }

        SQLiteDatabase database = SqliteHelper.Instance(context).getWritableDatabase();

        if (!database.isOpen()) {
            Log.d("", "db is not open.");
            return false;
        }
        Table table = cls.getAnnotation(Table.class);
        database.delete(table.value(),BaseModel.IdColumn+"=?",new String[]{id.toString()});
        database.close();
        return true;
    }

    /**
     * 依据主键修改
     *
     * @param item
     * @return
     */
    public boolean update(T item) {
        Integer id = item.getId();
        if (id == null) {
            return false;
        }

        Class<T> cls = getModelCls();
        if (!cls.isAnnotationPresent(Table.class)) {
            return false;
        }

        SQLiteDatabase database = SqliteHelper.Instance(context).getWritableDatabase();

        if (!database.isOpen()) {
            Log.d("", "db is not open.");
            return false;
        }

        Table table = cls.getAnnotation(Table.class);

        ContentValues contentValues = getContentValues(item);

        int re = database.update(table.value(), contentValues, BaseModel.IdColumn+"=?", new String[]{id.toString()});

        database.close();

        return re > 0;
    }

    public T get(Integer id) {
        Class<T> cls = getModelCls();
        if (!cls.isAnnotationPresent(Table.class)) {
            return null;
        }
        SQLiteDatabase database = SqliteHelper.Instance(context).getReadableDatabase();

        if (!database.isOpen()) {
            Log.d("", "db is not open.");
            return null;
        }

        Table table = cls.getAnnotation(Table.class);
        List<ColumnDto> columnDtos = new ArrayList<>();
        this.findColumns(cls, columnDtos);

        String[] columns = (String[]) columnDtos.stream().map(c -> c.getColumnName()).toArray();

        Cursor query = database.query(table.value(), columns, "_id=?", new String[]{id.toString()}, null, null, null);

        T item = null;

        while (query.moveToNext()) {
            item = getItemFromCursor(cls, columnDtos, query);
        }

        database.close();
        return item;
    }


    /**
     * 查询列表
     * @param where
     * @param values
     * @return
     */
    public List<T> query(String where, String[] values) {
        Class<T> cls = getModelCls();
        if (!cls.isAnnotationPresent(Table.class)) {
            return null;
        }
        SQLiteDatabase database = SqliteHelper.Instance(context).getReadableDatabase();

        if (!database.isOpen()) {
            Log.d("", "db is not open.");
            return null;
        }

        List<T> list = new ArrayList<>();

        Table table = cls.getAnnotation(Table.class);
        List<ColumnDto> columnDtos = new ArrayList<>();
        this.findColumns(cls, columnDtos);

        List<String> columnList = columnDtos.stream().map(c -> c.getColumnName()).collect(Collectors.toList());
        String[] columns = columnList.stream().toArray(String[]::new);

        Cursor query = database.query(table.value(), columns, where, values, null, null, null);
        while (query.moveToNext()) {
            T item = getItemFromCursor(cls, columnDtos, query);
            list.add(item);
        }
        database.close();
        return list;
    }


    /**
     * 找到所有的列
     *
     * @param cls
     * @param columns
     */
    protected static void findColumns(Class cls, List<ColumnDto> columns) {
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Column.class)) {
                continue;
            }
            Column annotation = field.getAnnotation(Column.class);
            ColumnDto columnDto = new ColumnDto();
            columnDto.setFieldName(field.getName());
            columnDto.setColumnType(annotation.columnType());
            columnDto.setColumnName(annotation.value());
            columnDto.setAutoIncrement(annotation.autoIncrement());
            columnDto.setPrimaryKey(annotation.primaryKey());
            try {
                columnDto.setConverter((ITypeConverter) annotation.converterType().newInstance());
            } catch (IllegalAccessException|InstantiationException e) {
                throw new RuntimeException(e);
            }
            columns.add(columnDto);
        }
        Class superclass = cls.getSuperclass();
        if (!superclass.equals(Object.class)) {
            findColumns(superclass, columns);
        }
    }


    @NonNull
    private static <T extends BaseModel> T getItemFromCursor(Class<T> cls, List<ColumnDto> columnDtos, Cursor query) {
        T item;
        try {
            item = cls.newInstance();
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException(e);
        }
        for (ColumnDto columnDto : columnDtos) {
            Field field = ReflectionUtils.getDeclaredField(cls,columnDto.getFieldName());
            field.setAccessible(true);
            int columnIndex = query.getColumnIndex(columnDto.getColumnName());
            try {
                switch (columnDto.getColumnType()) {
                    case Integer:
                        int anInt = query.getInt(columnIndex);
                        field.set(item, anInt);
                        break;
                    case REAL:
                        double aDouble = query.getDouble(columnIndex);
                        field.set(item, columnDto.getConverter().convert(aDouble));
                        break;
                    case BLOB:
                        byte[] blob = query.getBlob(columnIndex);
                        field.set(item, blob);
                        break;
                    case Text:
                        String text = query.getString(columnIndex);
                        field.set(item, columnDto.getConverter().convert(text));
                        break;
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return item;
    }

    /**
     * 获取内容值
     *
     * @param item
     * @return
     */
    @NonNull
    private ContentValues getContentValues(T item) {
        Class cls = getModelCls();
        List<ColumnDto> columns = new ArrayList<>();
        this.findColumns(cls, columns);

        // 创建ContentValues对象
        ContentValues values = new ContentValues();
        for (ColumnDto column : columns) {
            if (column.isAutoIncrement()) {
                continue;
            }
            Field field = null;
            Object fieldValue = null;

            try {
                field = ReflectionUtils.getDeclaredField(cls,column.getFieldName());
                field.setAccessible(true);
                fieldValue = field.get(item);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            switch (column.getColumnType()) {
                case Text:
                    values.put(column.getColumnName(), fieldValue == null ? null : fieldValue.toString());
                    break;
                case BLOB:
                    values.put(column.getColumnName(), fieldValue == null ? null : (byte[]) fieldValue);
                    break;
                case REAL:
                    values.put(column.getColumnName(), fieldValue == null ? null : (Double) fieldValue);
                    break;
                case Integer:
                    values.put(column.getColumnName(), fieldValue == null ? null : (Integer) fieldValue);
                    break;
            }
        }
        return values;
    }


}
